﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Xml;
using PropertyGrid;
using MockupDesigner.Editors;
using MockupDesigner.Items;
using MockupDesigner.AppItems.Commands;
using System.Windows.Shapes;
using MockupDesigner.AppItems.Controls;

namespace MockupDesigner.AppItems
{
    public partial class DesignerCanvas : Canvas
    {
        private Point? rubberbandSelectionStartPoint = null;

        public PropertyGrid.PropertyGrid PropertyGrid { get; set; }
        public MetadataControl MetadataControl { get; set; }

        private SelectionService selectionService;
        public SelectionService SelectionService
        {
            get
            {
                if (selectionService == null)
                    selectionService = new SelectionService(this);

                return selectionService;
            }
        }

        private DocumentSerializationService serializationService;
        public DocumentSerializationService SerializationService
        {
            get
            {
                if (serializationService == null)
                    serializationService = new DocumentSerializationService() { DesignerCanvas = this };

                return serializationService;
            }
        }

        private ItemEditor currentEditor;

        public CommandProcessor CommandProcessor { get; private set; }

        public DesignerCanvas()
        {
            this.CommandProcessor = new CommandProcessor();
            this.AllowDrop = true;
        }


        public void UpdateZIndex()
        {
            List<UIElement> ordered = (from UIElement item in this.Children
                                       orderby Canvas.GetZIndex(item as UIElement)
                                       select item as UIElement).ToList();

            for (int i = 0; i < ordered.Count; i++)
            {
                Canvas.SetZIndex(ordered[i], i);
            }
        }


        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);
            if (e.Source == this)
            {
                // in case that this click is the start of a 
                // drag operation we cache the start point
                this.rubberbandSelectionStartPoint = new Point?(e.GetPosition(this));

                // if you click directly on the canvas all 
                // selected items are 'de-selected'
                SelectionService.ClearSelection();
                Focus();
                e.Handled = true;
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            // if mouse button is not pressed we have no drag operation, ...
            if (e.LeftButton != MouseButtonState.Pressed)
                this.rubberbandSelectionStartPoint = null;

            // ... but if mouse button is pressed and start
            // point value is set we do have one
            if (this.rubberbandSelectionStartPoint.HasValue)
            {
                // create rubberband adorner
                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
                if (adornerLayer != null)
                {
                    RubberbandAdorner adorner = new RubberbandAdorner(this, rubberbandSelectionStartPoint);
                    if (adorner != null)
                    {
                        adornerLayer.Add(adorner);
                    }
                }
            }

            e.Handled = true;
        }

        protected override void OnDrop(DragEventArgs e)
        {
            base.OnDrop(e);
            DragObject dragObject = e.Data.GetData(typeof(DragObject)) as DragObject;
            if (dragObject != null && !String.IsNullOrEmpty(dragObject.Xaml))
            {
                List<DesignerItem> newItems = new List<DesignerItem>();
                Object content = XamlReader.Load(XmlReader.Create(new StringReader(dragObject.Xaml)));
                if (content != null)
                {
                    if (content is DesignerItem)
                    {
                        DesignerItem di = (DesignerItem)content;
                        di.IsInToolbox = false;
                        if (di.Content is StackPanel)
                        {
                            StackPanel c = (StackPanel)di.Content;
                            foreach (DesignerItem item in ((StackPanel)di.Content).Children)
                            {
                                item.IsInToolbox = false;
                                newItems.Add(item);
                            }
                            newItems.Sort(delegate(DesignerItem di1, DesignerItem di2)
                            {
                                return Canvas.GetZIndex(di1).CompareTo(Canvas.GetZIndex(di2));
                            });
                            c.Children.Clear();
                        }
                        else
                        {
                            newItems.Add((DesignerItem)XamlReader.Load(XmlReader.Create(new StringReader(XamlWriter.Save(content)))));
                        }
                    }
                    else
                    {
                        DesignerItem newItem = new DesignerItem();
                        newItem.IsInToolbox = false;
                        newItem.Content = content;
                        newItems.Add(newItem);
                    }

                    Point position = e.GetPosition(this);

                    foreach (DesignerItem newItem in newItems)
                    {
                        if (newItems.Count == 1)
                        {
                            if (dragObject.DesiredSize.HasValue && (!double.IsNaN(dragObject.DesiredSize.Value.Height) && !double.IsNaN(dragObject.DesiredSize.Value.Width)))
                            {
                                Size desiredSize = dragObject.DesiredSize.Value;
                                newItem.Width = desiredSize.Width;
                                newItem.Height = desiredSize.Height;

                                DesignerCanvas.SetLeft(newItem, Math.Max(0, position.X - newItem.Width / 2));
                                DesignerCanvas.SetTop(newItem, Math.Max(0, position.Y - newItem.Height / 2));
                            }
                            else
                            {
                                DesignerCanvas.SetLeft(newItem, Math.Max(0, position.X));
                                DesignerCanvas.SetTop(newItem, Math.Max(0, position.Y));
                            }
                        }
                        else
                        {
                            DesignerCanvas.SetLeft(newItem, Math.Max(0, DesignerCanvas.GetLeft(newItem) + position.X));
                            DesignerCanvas.SetTop(newItem, Math.Max(0, DesignerCanvas.GetTop(newItem) + position.Y));
                        }
                        DesignerCanvas.SetZIndex(newItem, this.Children.Count);
                    }

                    CommandProcessor.ExecuteCommand(new InsertCommand(), this, newItems);
                    
                    //We have to verify if it is a Custom Control (group?) or not
                    if (newItems.Count == 1) newItems[0].Focus();
                    else
                    {
                        this.SelectionService.ClearSelection();
                        foreach (DesignerItem item in newItems)
                            this.SelectionService.AddToSelection(item);
                        GroupCommand command = new GroupCommand();
                        command.Execute(this, null);
                        DesignerCanvas.SetLeft(command.Group, Math.Max(0, position.X));
                        DesignerCanvas.SetTop(command.Group, Math.Max(0, position.Y));
                    }
                }

                e.Handled = true;
            }
        }

        protected override Size MeasureOverride(Size constraint)
        {
            Size size = new Size();

            foreach (UIElement element in this.InternalChildren)
            {
                double left = Canvas.GetLeft(element);
                double top = Canvas.GetTop(element);
                left = double.IsNaN(left) ? 0 : left;
                top = double.IsNaN(top) ? 0 : top;

                //measure desired size for each child
                element.Measure(constraint);

                Size desiredSize = element.DesiredSize;
                if (!double.IsNaN(desiredSize.Width) && !double.IsNaN(desiredSize.Height))
                {
                    size.Width = Math.Max(size.Width, left + desiredSize.Width);
                    size.Height = Math.Max(size.Height, top + desiredSize.Height);
                }
            }
            // add margin 
            size.Width += 10;
            size.Height += 10;
            return size;
        }

        public void RemoveCurrentEditor()
        {
            if (this.currentEditor != null)
            {
                if (this.currentEditor.Element != null)
                {
                    this.Children.Remove(this.currentEditor.Element);
                }
                this.currentEditor = null;
            }
        }

        public void SetCurrentEditor(ItemEditor editor, double top, double left)
        {
            this.SelectionService.ClearSelectionButNotPropertyGrid();
            this.currentEditor = editor;
            if (this.currentEditor.Element != null)
            {
                this.Children.Add(this.currentEditor.Element);
                Canvas.SetTop(this.currentEditor.Element, top);
                Canvas.SetLeft(this.currentEditor.Element, left);
                Canvas.SetZIndex(this.currentEditor.Element, 1000);
                this.currentEditor.Element.Focus();
            }
        }
    }
}
